home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 52 / Amiga Format AFCD52 (Issue 136, May 2000).iso / -serious- / cd-rom / showtoc / showtoc.c < prev    next >
C/C++ Source or Header  |  2000-02-28  |  19KB  |  582 lines

  1. /* :ts=3
  2. ** ShowTOC - shows Table Of Content of an AudioCD in a readable and
  3. **           freely configurable way
  4. **
  5. ** written & © Ralph Reuchlein <amiga@rripley.de> 2000
  6. **
  7. ** This software is eMailware, which means, that if you like this
  8. ** piece of software, you have to write me an eMail (address see end
  9. ** of readme).
  10. */
  11.  
  12. #include <ctype.h>
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <stdarg.h>
  16.  
  17. #include <exec/types.h>
  18. #include <proto/exec.h>
  19. #include <proto/dos.h>
  20. #include <clib/alib_protos.h>
  21. #include <exec/memory.h>
  22. #include <devices/scsidisk.h>
  23.  
  24. /* a 10 byte SCSI command */
  25. typedef struct {
  26.    UBYTE  opcode;
  27.    UBYTE  b1;
  28.    UBYTE  b2;
  29.    UBYTE  b3;
  30.    UBYTE  b4;
  31.    UBYTE  b5;
  32.    UBYTE  b6;
  33.    UBYTE  b7;
  34.    UBYTE  b8;
  35.    UBYTE  control;
  36. } SCSICMD10;
  37.  
  38. typedef struct {
  39.    UBYTE pad0;
  40.    UBYTE trackType;
  41.    UBYTE trackNum;
  42.    UBYTE pad1;
  43.    ULONG startFrame;
  44. } TOCENTRY;
  45.  
  46. typedef struct {
  47.    UWORD    length;
  48.    UBYTE    firstTrack;
  49.    UBYTE    lastTrack;
  50.    TOCENTRY tocs[100];
  51. } TOC;
  52.  
  53. typedef struct {
  54.    UBYTE mins;
  55.    UBYTE secs;
  56.    UBYTE frames;
  57.    UBYTE fraction;
  58. } TIMEINFO;
  59.  
  60. #define SCSI_CMD_READTOC   0x43
  61. #define AUDIO_FRAMESIZE    2352
  62. #define FRAMES_PER_SECOND  75
  63. #define SECONDS_PER_MINUTE 60
  64. #define SENSE_LEN          252
  65.  
  66. #define CDID_ARTIST_INDEX  0
  67. #define CDID_TITLE_INDEX   1
  68. #define CDID_TRACK_INDEX   2
  69.  
  70. const UBYTE ver_string[] = "$VER: ShowTOC 1.0 (08.01.00)Copyright © 2000 by Ralph Reuchlein";
  71.  
  72.  
  73. /* ReadArgs defines ----------------------------------------------- */
  74. #define ARGS_TEMPLATE   "DEVICE=D/K/A,"\
  75.                         "UNIT=U/N/K/A,"\
  76.                         "FORMAT=FMT/K,"\
  77.                         "INTRODUCER=INTR/K,"\
  78.                         "CDIDDIR/K,"\
  79.                         "HEADFORMAT=HEADFMT/K,"\
  80.                         "TAILFORMAT=TAILFMT/K,"\
  81.                         "NOHEADER/S,"\
  82.                         "NOLIST/S,"\
  83.                         "NOTAIL/S"
  84.  
  85. enum {
  86.    OPT_DEVICE = 0,
  87.    OPT_UNIT,
  88.    OPT_FMT,
  89.    OPT_INTR,
  90.    OPT_CDIDDIR,
  91.    OPT_HEADFMT,
  92.    OPT_TAILFMT,
  93.    OPT_NOHEAD,
  94.    OPT_NOLIST,
  95.    OPT_NOTAIL,
  96.  
  97.    OPT_COUNT
  98. };
  99. static LONG args[OPT_COUNT];
  100.  
  101. #define ARG_DEVICE   (  (STRPTR)args[OPT_DEVICE])
  102. #define ARG_UNIT     (*(ULONG *)args[OPT_UNIT])
  103. #define ARG_FMT      (  (STRPTR)args[OPT_FMT])
  104. #define ARG_HEADFMT  (  (STRPTR)args[OPT_HEADFMT])
  105. #define ARG_TAILFMT  (  (STRPTR)args[OPT_TAILFMT])
  106. #define ARG_INTR     (  (STRPTR)args[OPT_FMT])
  107. #define ARG_CDIDDIR  (  (STRPTR)args[OPT_CDIDDIR])
  108. #define ARG_NOHEAD   (args[OPT_NOHEAD]==-1)
  109. #define ARG_NOLIST   (args[OPT_NOLIST]==-1)
  110. #define ARG_NOTAIL   (args[OPT_NOTAIL]==-1)
  111.  
  112. #define IS_DEVICE    (args[OPT_DEVICE])
  113. #define IS_UNIT      (args[OPT_UNIT])
  114. #define IS_FMT       (args[OPT_FMT])
  115. #define IS_HEADFMT   (args[OPT_HEADFMT])
  116. #define IS_TAILFMT   (args[OPT_TAILFMT])
  117. #define IS_INTR      (args[OPT_INTR])
  118. #define IS_CDIDDIR   (args[OPT_CDIDDIR])
  119. #define IS_NOHEAD    (args[OPT_NOHEAD]==-1)
  120. #define IS_NOLIST    (args[OPT_NOLIST]==-1)
  121. #define IS_NOTAIL    (args[OPT_NOTAIL]==-1)
  122.  
  123. #define DEFAULT_INTRODUCER             '%'
  124.  
  125. #define DEFAULT_HEAD                   (STRPTR)"       CD: %i%\\n    Title: %T%\\nArtist(s): %a"
  126. #define DEFAULT_FORMAT                 (STRPTR)"Track  %2n: %d  %t"
  127. #define DEFAULT_TAIL                   (STRPTR)"CD length: %f"
  128.  
  129. #define DEFAULT_TIME_START_FORMAT      (STRPTR)"%2sm:%02ss;%02sf"
  130. #define DEFAULT_TIME_END_FORMAT        (STRPTR)"%2em:%02es;%02ef"
  131. #define DEFAULT_TIME_DURATION_FORMAT   (STRPTR)"%2dm:%02ds;%02df"
  132. #define DEFAULT_TIME_FULL_FORMAT       (STRPTR)"%2fm:%02fs;%02ff"
  133.  
  134. #define CDID_FORMAT                    (STRPTR)"ID%02d%06X%06X"
  135.  
  136.  
  137.  
  138. /* Prototypes ----------------------------------------------------- */
  139. void     error(STRPTR,...);
  140. TOC     *scanTOC(STRPTR,UBYTE);
  141. void     getTimeInfo(TIMEINFO *,ULONG);
  142. STRPTR  *readCDID(STRPTR);
  143. void     freeCDID(STRPTR *);
  144. void     printFmt(STRPTR,TOC *,STRPTR *,UBYTE,BOOL);
  145.  
  146. static BYTE io_error;
  147.  
  148. #define  BUF_SIZE    256
  149. UBYTE buf[BUF_SIZE];
  150.  
  151.  
  152. /* ---------------------------------------------------------------- */
  153. void main(UWORD argc,STRPTR argv[]) {
  154.    struct RDArgs *rdargs;
  155.    TOC           *toc;
  156.    STRPTR        *cdids=NULL;
  157.  
  158.    /* process arguments */
  159.    if (rdargs=ReadArgs(ARGS_TEMPLATE,args,NULL)) {
  160.       if ((toc=scanTOC(ARG_DEVICE,ARG_UNIT))) {
  161.          register int i;
  162.          UBYTE tracks=(toc->length/sizeof(TOCENTRY))-1;
  163.          if (IS_CDIDDIR) {
  164.             /* if CDIDDIR is specified, then build filename and try to
  165.                read in CDID data */
  166.             sprintf(buf,"%s",ARG_CDIDDIR);
  167.             AddPart(buf,"",BUF_SIZE);
  168.             sprintf(&buf[strlen(buf)],CDID_FORMAT,tracks,toc->tocs[2].startFrame,toc->tocs[tracks].startFrame);
  169.             cdids=readCDID(buf);
  170.          }
  171.          if (!IS_NOHEAD)   printFmt((IS_HEADFMT)?(ARG_HEADFMT):(DEFAULT_HEAD),toc,cdids,0,TRUE);
  172.          if (!IS_NOLIST)   for(i=0;i<tracks;i++) {
  173.             printFmt((IS_FMT)?(ARG_FMT):(DEFAULT_FORMAT),toc,cdids,i,TRUE);
  174.          }
  175.          if (!IS_NOTAIL)   printFmt((IS_TAILFMT)?(ARG_TAILFMT):(DEFAULT_TAIL),toc,cdids,0,TRUE);
  176.          if (cdids)  freeCDID(cdids);
  177.          FreeVec((APTR)toc);
  178.       }
  179.       else {
  180.          error("Error accessing %s, unit %i: error $%02x\n"
  181.                "(maybe no CD-ROM drive or no media?)\n",ARG_DEVICE,ARG_UNIT,io_error);
  182.       }
  183.       FreeArgs(rdargs);
  184.    }
  185.    else {
  186.       error("Usage: %s %s\n",argv[0],ARGS_TEMPLATE);
  187.    }
  188. }
  189.  
  190.  
  191.  
  192. /* ---------------------------------------------------------------- */
  193. void error(STRPTR fmt,...) {
  194.    va_list a;
  195.    va_start(a,fmt);
  196.    vfprintf(stdout,fmt,a);
  197.    va_end(a);
  198. }
  199.  
  200.  
  201.  
  202. /* ---------------------------------------------------------------- */
  203.  
  204. TOC *scanTOC(STRPTR scsiname,UBYTE unit) {
  205.    static struct MsgPort  *msgPort;
  206.    static struct IOStdReq *ioReq;
  207.    static struct SCSICmd   scsicmd;
  208.    static SCSICMD10       *scsi;
  209.    static TOC             *toc;
  210.    static UBYTE           *sense;
  211.    BOOL                    ok=FALSE;
  212.  
  213.    if ((scsi = (SCSICMD10 *)AllocVec(sizeof(SCSICMD10),MEMF_CLEAR|MEMF_CHIP))) {
  214.       if ((toc = (TOC *)AllocVec(sizeof(TOC),MEMF_CLEAR|MEMF_CHIP))) {
  215.          if ((sense = (UBYTE *)AllocVec(SENSE_LEN,MEMF_CLEAR|MEMF_CHIP))) {
  216.             /* create MsgPort */
  217.             if ((msgPort=CreatePort(NULL,0))!=NULL) {
  218.                /* create IOStdReq */
  219.                if ((ioReq=CreateStdIO(msgPort))!=NULL) { 
  220.                   /* open the SCSI device */
  221.                   if (!OpenDevice(scsiname,unit,(struct IORequest *)ioReq,0)) {
  222.                      /* fill IOStdReq */
  223.                      ioReq->io_Length  = sizeof(struct SCSICmd);
  224.                      ioReq->io_Data    = (APTR)&scsicmd;
  225.                      ioReq->io_Command = HD_SCSICMD;
  226.                      /* fill SCSICmd */
  227.                      scsicmd.scsi_Data          = (APTR)toc;
  228.                      scsicmd.scsi_Length        = sizeof(TOC);
  229.                      scsicmd.scsi_Command       = (APTR)scsi;
  230.                      scsicmd.scsi_CmdLength     = sizeof(SCSICMD10);
  231.                      scsicmd.scsi_Flags         = (SCSIF_READ | SCSIF_AUTOSENSE);
  232.                      scsicmd.scsi_SenseData     = (APTR)sense;
  233.                      scsicmd.scsi_SenseLength   = SENSE_LEN;
  234.                      scsicmd.scsi_SenseActual   = 0;
  235.                      /* fill scsi */
  236.                      scsi->opcode      = SCSI_CMD_READTOC;
  237.                      scsi->b1          = 0;
  238.                      scsi->b2          = 0;
  239.                      scsi->b3          = 0;
  240.                      scsi->b4          = 0;
  241.                      scsi->b5          = 0;
  242.                      scsi->b6          = 0;
  243.                      scsi->b7          = (sizeof(TOC)>>8);
  244.                      scsi->b8          = (sizeof(TOC)&0xFF);
  245.                      scsi->control     = 0;
  246.  
  247.                      /* do the request */
  248.                      if (DoIO((struct IORequest *)ioReq)==0) {;
  249.                         ok=TRUE;
  250.                      }
  251.                      io_error = ioReq->io_Error;
  252.  
  253.                      /* close device */
  254.                      CloseDevice((struct IORequest *)ioReq);
  255.                      /* and remove all needed resources */
  256.                   }
  257.                   DeleteExtIO((struct IORequest *)ioReq);
  258.                }
  259.                DeletePort(msgPort);
  260.             }
  261.             FreeVec((APTR)sense);
  262.          }
  263.          /* do not free TOC, because we need the data */
  264.       }
  265.       FreeVec((APTR)scsi);
  266.    }
  267.    if (ok) {
  268.       return toc;
  269.    }
  270.    else {
  271.       FreeVec((APTR)toc);
  272.    }
  273.    return NULL;
  274. }
  275.  
  276.  
  277. /* ---------------------------------------------------------------- */
  278.  
  279. void getTimeInfo(TIMEINFO *ti,ULONG frames) {
  280.    ti->frames = frames % FRAMES_PER_SECOND;
  281.    ti->fraction = (UBYTE)(100 * (double)ti->frames / (double)FRAMES_PER_SECOND + 0.5);
  282.    frames /= FRAMES_PER_SECOND;
  283.    ti->secs = (UBYTE)(frames % SECONDS_PER_MINUTE);
  284.    frames /= SECONDS_PER_MINUTE;
  285.    ti->mins = (UBYTE) frames;
  286. }
  287.  
  288.  
  289.  
  290. /* ---------------------------------------------------------------- */
  291.  
  292. STRPTR *readCDID(STRPTR filename) {
  293.    ULONG    filelen=-1;
  294.    FILE    *fh;
  295.    STRPTR   cdid;
  296.    STRPTR  *cdids=NULL;
  297.  
  298.    /* try to examine file length */
  299.    struct FileInfoBlock *fib=AllocDosObject(DOS_FIB,NULL);
  300.    if (fib) {
  301.       BPTR l;
  302.       /* lock the file */
  303.       if (l=Lock(filename,ACCESS_READ)) {
  304.          /* examine file */
  305.          if (Examine(l,fib)) {
  306.             filelen=fib->fib_Size;
  307.          }
  308.          else {
  309.             error("%s: Couldn't examine file information!\n",filename);
  310.          }
  311.          UnLock(l);
  312.       }
  313.       else {
  314.          /*error("%s: Couldn't access to this file (maybe not found?)!\n",filename);*/
  315.       }
  316.       FreeDosObject(DOS_FIB,fib);
  317.    }
  318.    else {
  319.       error("%s: Couldn't allocate FileInfoBlock memory!\n",filename);
  320.    }
  321.    /* file length couldn't be examined (file not found etc.), return */
  322.    if (filelen==-1)  return NULL;
  323.  
  324.    /* read in CDID file and store line pointers */
  325.    if (fh=fopen(filename,"r")) {
  326.       if (cdid=AllocVec(filelen+1,MEMF_CLEAR|MEMF_REVERSE)) {
  327.          if (fread(cdid,1,filelen,fh)==filelen) {
  328.             register STRPTR cdidtmp=cdid;
  329.             register UWORD  lines=0;
  330.             cdid[filelen]=0;     /* terminate string */
  331.             /* scan the buffer for newlines */
  332.             while (*cdidtmp) {
  333.                if (*(cdidtmp++)=='\n')  lines++;
  334.             }
  335.             /* allocate pointer list */
  336.             if (cdids=(STRPTR *)AllocVec((lines+1)*sizeof(STRPTR),MEMF_CLEAR|MEMF_REVERSE)) {
  337.                /* find line starts and store their pointers */
  338.                cdids[0]=cdid;
  339.                cdidtmp=cdid; lines=1;
  340.                while (*cdidtmp) {
  341.                   if (*cdidtmp=='\n') {
  342.                      *cdidtmp=0;
  343.                      cdids[lines++]=(cdidtmp+1);
  344.                   }
  345.                   cdidtmp++;
  346.                }
  347.                cdids[lines]=NULL;
  348.                /* cdids not freed! */
  349.             }
  350.             else {
  351.                error("Couldn't allocate %i bytes for CDID pointer data.\n",(lines+1)*sizeof(STRPTR));
  352.             }
  353.          }
  354.          else {
  355.             error("%s: Reading error on CDID file!\n",filename);
  356.          }
  357.          /* cdid not freed! */
  358.       }
  359.       else {
  360.          error("Couldn't allocate %i bytes for CDID data.\n",filelen+1);
  361.       }
  362.       fclose(fh);
  363.    }
  364.    else {
  365.       error("%s: Couldn't open CDID file for reading!\n",filename);
  366.    }
  367.    return cdids;
  368. }
  369.  
  370. void freeCDID(STRPTR *cdids) {
  371.    if (cdids) {
  372.       FreeVec((APTR)cdids[0]);
  373.       FreeVec((APTR)cdids);
  374.    }
  375. }
  376.  
  377.  
  378.  
  379. /* ---------------------------------------------------------------- */
  380. #define FMT_STRING      's'
  381. #define FMT_INTEGER     'i'
  382.  
  383. void printFmt(STRPTR fmt,TOC *toc,STRPTR *cdids,UBYTE trackNum,BOOL newLine) {
  384.    register int tmp0,tmp1,intr;
  385.    STRPTR   fmt_end = fmt + strlen(fmt),tmp_fmt,str2;
  386.    TIMEINFO ti;
  387.    ULONG    frames;
  388.    UBYTE    tracks=(toc->length/sizeof(TOCENTRY))-1;
  389.  
  390.    /* if there is no format, return */
  391.    if (!fmt) return;
  392.  
  393.    /* if toc is too small, return */
  394.    if (toc->length <= 6) return;
  395.  
  396.    /* do we have specified a special introducer? */
  397.    if (ARG_INTR) {
  398.       intr = *ARG_INTR;    /* only the first character */
  399.    }
  400.    else {
  401.       intr = DEFAULT_INTRODUCER;
  402.    }
  403.  
  404.    /* we process the format string: replace the format types by
  405.       C-format types and process the format portion one by one */
  406.    tmp_fmt=fmt;
  407.    while(fmt<fmt_end) {
  408.       tmp_fmt=fmt;
  409.       /* search for the introducer */
  410.       while ((*fmt!=intr) && (fmt<fmt_end)) fmt++; if (fmt>=fmt_end) break;
  411.       /* write string til introducer */
  412.       *fmt=0; fputs(tmp_fmt,stdout); *fmt='%';     /* fprintf() only uses '%'! */
  413.       tmp_fmt=fmt;
  414.       /* search for the terminating format type character */
  415.       while ((*fmt!='\\') && (!isalpha(*fmt)) && (fmt<fmt_end)) fmt++; if (fmt>=fmt_end) break;
  416.       tmp0 = *fmt; tmp1=*(fmt+1);
  417.       switch (tmp0) {
  418.          case '\\':     /* special characters */
  419.             switch (tmp1) {
  420.                case 'n':   fputs("\n",stdout); break;
  421.                case 'r':   fputs("\r",stdout); break;
  422.                case 'f':   fputs("\f",stdout); break;
  423.                case 't':   fputs("\t",stdout); break;
  424.             }
  425.             fmt+=2;
  426.             break;
  427.          case 's':      /* start time of track */
  428.          case 'e':      /* end time of track */
  429.          case 'd':      /* duration of track */
  430.          case 'f':      /* full CD time */
  431.             switch (tmp0) {
  432.                case 's':
  433.                   frames=toc->tocs[trackNum].startFrame;
  434.                   str2=DEFAULT_TIME_START_FORMAT;
  435.                   break;
  436.                case 'e':
  437.                   frames=toc->tocs[trackNum+1].startFrame;
  438.                   str2=DEFAULT_TIME_END_FORMAT;
  439.                   break;
  440.                case 'd':
  441.                   frames=toc->tocs[trackNum+1].startFrame-toc->tocs[trackNum].startFrame;
  442.                   str2=DEFAULT_TIME_DURATION_FORMAT;
  443.                   break;
  444.                case 'f':
  445.                   frames=toc->tocs[tracks].startFrame;
  446.                   str2=DEFAULT_TIME_FULL_FORMAT;
  447.                   break;
  448.                default:
  449.                    frames=0; str2="";   /* should never be reached! */
  450.             }
  451.             getTimeInfo(&ti,frames);
  452.             switch(tmp1) {
  453.                case 'm':
  454.                   *fmt=FMT_INTEGER; *(fmt+1)=0;
  455.                   fprintf(stdout,tmp_fmt,ti.mins);
  456.                   *fmt=tmp0; fmt++; *fmt=tmp1; fmt++;
  457.                   break;
  458.                case 's':
  459.                   *fmt=FMT_INTEGER; *(fmt+1)=0;
  460.                   fprintf(stdout,tmp_fmt,ti.secs);
  461.                   *fmt=tmp0; fmt++; *fmt=tmp1; fmt++;
  462.                   break;
  463.                case 'f':
  464.                   *fmt=FMT_INTEGER; *(fmt+1)=0;
  465.                   fprintf(stdout,tmp_fmt,ti.frames);
  466.                   *fmt=tmp0; fmt++; *fmt=tmp1; fmt++;
  467.                   break;
  468.                case 'd':
  469.                   *fmt=FMT_INTEGER; *(fmt+1)=0;
  470.                   fprintf(stdout,tmp_fmt,ti.fraction);
  471.                   *fmt=tmp0; fmt++; *fmt=tmp1; fmt++;
  472.                   break;
  473.                case 'a':
  474.                   *fmt=FMT_INTEGER; *(fmt+1)=0;
  475.                   fprintf(stdout,tmp_fmt,frames);
  476.                   *fmt=tmp0; fmt++; *fmt=tmp1; fmt++;
  477.                   break;
  478.                case 'n':
  479.                   if ((tmp0=='s') || (tmp0=='e')) {
  480.                      *fmt=FMT_INTEGER; *(fmt+1)=0;
  481.                      fprintf(stdout,tmp_fmt,(tmp0=='s')?(toc->firstTrack):(toc->lastTrack));
  482.                      *fmt=tmp0; fmt++; *fmt=tmp1; fmt++;
  483.                      break;
  484.                   }
  485.                default:
  486.                   printFmt(str2,toc,cdids,trackNum,FALSE);
  487.                   fmt++;
  488.             }
  489.             break;
  490.  
  491.          case 'n':   /* track number */
  492.             *fmt=FMT_INTEGER; *(fmt+1)=0;
  493.             fprintf(stdout,tmp_fmt,toc->tocs[trackNum].trackNum);
  494.             *fmt=tmp0; fmt++; *fmt=tmp1;
  495.             break;
  496.  
  497.          case 'N':   /* number of tracks */
  498.             *fmt=FMT_INTEGER; *(fmt+1)=0;
  499.             fprintf(stdout,tmp_fmt,tracks);
  500.             *fmt=tmp0; fmt++; *fmt=tmp1;
  501.             break;
  502.  
  503.          case 'b':   /* track length in bytes */
  504.             *fmt=FMT_INTEGER; *(fmt+1)=0;
  505.             frames=toc->tocs[trackNum+1].startFrame-toc->tocs[trackNum].startFrame;
  506.             fprintf(stdout,tmp_fmt,frames*AUDIO_FRAMESIZE);
  507.             *fmt=tmp0; fmt++; *fmt=tmp1;
  508.             break;
  509.  
  510.          case 'y':   /* type of track */
  511.             *fmt=FMT_STRING; *(fmt+1)=0;
  512.             fprintf(stdout,tmp_fmt,(toc->tocs[trackNum].trackType & 0x04) ? "Data" : "Audio");
  513.             *fmt=tmp0; fmt++; *fmt=tmp1;
  514.             break;
  515.  
  516.          case 'c':   /* number of channels in track as numeric value */
  517.             *fmt=FMT_INTEGER; *(fmt+1)=0;
  518.             fprintf(stdout,tmp_fmt,(toc->tocs[trackNum].trackType & 0x08) ? 4 : 2);
  519.             *fmt=tmp0; fmt++; *fmt=tmp1;
  520.             break;
  521.  
  522.          case 'C':   /* number of channels in track as text */
  523.             *fmt=FMT_STRING; *(fmt+1)=0;
  524.             fprintf(stdout,tmp_fmt,(toc->tocs[trackNum].trackType & 0x08) ? "four" : "two");
  525.             *fmt=tmp0; fmt++; *fmt=tmp1;
  526.             break;
  527.  
  528.          case 'p':   /* Is digital copy of track permitted or prohibited? */
  529.             *fmt=FMT_STRING; *(fmt+1)=0;
  530.             fprintf(stdout,tmp_fmt,(toc->tocs[trackNum].trackType & 0x02) ? "permitted" : "prohibited");
  531.             *fmt=tmp0; fmt++; *fmt=tmp1;
  532.             break;
  533.  
  534.          case 'P':   /* Is digital copy of track permitted or prohibited? */
  535.             *fmt=FMT_STRING; *(fmt+1)=0;
  536.             fprintf(stdout,tmp_fmt,(toc->tocs[trackNum].trackType & 0x02) ? "y" : "n");
  537.             *fmt=tmp0; fmt++; *fmt=tmp1;
  538.             break;
  539.  
  540.          case 'i':   /* CD ID */
  541.             *fmt=FMT_INTEGER; *(fmt+1)=0;
  542.             fprintf(stdout,CDID_FORMAT,tracks,toc->tocs[2].startFrame,toc->tocs[tracks].startFrame);
  543.             *fmt=tmp0; fmt++; *fmt=tmp1;
  544.             break;
  545.  
  546.          case 'l':   /* TOC length on CD */
  547.             *fmt=FMT_INTEGER; *(fmt+1)=0;
  548.             fprintf(stdout,tmp_fmt,toc->length);
  549.             *fmt=tmp0; fmt++; *fmt=tmp1;
  550.             break;
  551.  
  552.          case 'T':   /* Title of CD (only if CDID file is found/provided) */
  553.             *fmt=FMT_STRING; *(fmt+1)=0;
  554.             if (cdids)  fprintf(stdout,tmp_fmt,cdids[CDID_TITLE_INDEX]);
  555.             *fmt=tmp0; fmt++; *fmt=tmp1;
  556.             break;
  557.  
  558.          case 't':   /* Title of track (only if CDID file is found/provided) */
  559.             *fmt=FMT_STRING; *(fmt+1)=0;
  560.             if (cdids)  fprintf(stdout,tmp_fmt,cdids[CDID_TRACK_INDEX+trackNum]);
  561.             *fmt=tmp0; fmt++; *fmt=tmp1;
  562.             break;
  563.  
  564.          case 'a':   /* Artist of CD (only if CDID file is found/provided) */
  565.             *fmt=FMT_STRING; *(fmt+1)=0;
  566.             if (cdids)  fprintf(stdout,tmp_fmt,cdids[CDID_ARTIST_INDEX]);
  567.             *fmt=tmp0; fmt++; *fmt=tmp1;
  568.             break;
  569.  
  570.          default:
  571.             *(fmt+1)=0;
  572.             fputs(tmp_fmt,stdout);
  573.             fmt++; *fmt=tmp1;
  574.       }
  575.       tmp_fmt=fmt;
  576.    }
  577.    if (tmp_fmt<fmt) {
  578.       fputs(tmp_fmt,stdout);
  579.    }
  580.    if (newLine)   fputc('\n',stdout);
  581. }
  582.